EKS で Community アドオンが使えるようになっていたので試してみた。metrics-server をアドオン経由でインストール可能に!
AWS マネジメントコンソールを確認していた所、作成時のデフォルト設定として最初から metrics-server がインストールされるようになっていました!
どうやら、Community アドオンと呼ばれる概念が生まれており、一部 OSS を EKS アドオンと同じように利用できるようになっています(現状は metrics-server のみ)。
You manage community add-ons just like existing Amazon EKS Add-ons. Community add-ons are different from existing add-ons in that they have a unique scope of support.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html
Community アドオンとして入れたソフトウェアのフルサポートはしないようです。
Importantly, AWS does not provide full support for community add-ons. AWS supports only lifecycle operations done using AWS APIs, such as installing add-ons or deleting add-ons.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html
一方で、インストール時の基本的なサポートは行ってくれます。
Basic Install Support by AWS
https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html
Community アドオン経由で入れた OSS のサポートが必要なら、直接 GitHub のリポジトリで issue を立てたりしてくれと書かれています。
全ての面倒を見るなら 従来の AWS アドオンと変わらないわけで、当然の建付けという所でしょう。
If you require support for a community add-on, utilize the existing project resources. For example, you may create a GitHub issue on the repo for the project.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html
ではさっそく試してみます。
アドオンを追加する
今回は Auto Mode を有効化しており、v1.31 の既存 EKS クラスターに追加します。
AWS Addon、Marketplace アドオンに加えて、Community add-ons が増えています。
metrics-server を選んで追加します。
一旦設定はデフォルトとします。
Community アドオンでも describe-addon-versions コマンドで、EKS バージョンとの互換性を確認可能です。
使い勝手はこれまでのアドオンと全く同じですね。
% aws eks describe-addon-versions --addon-name metrics-server
{
"addons": [
{
"addonName": "metrics-server",
"type": "observability",
"addonVersions": [
{
"addonVersion": "v0.7.2-eksbuild.1",
"architecture": [
"amd64",
"arm64"
],
"computeTypes": [
"ec2",
"hybrid",
"fargate",
"auto"
],
"compatibilities": [
{
"clusterVersion": "1.31",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.30",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.29",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.28",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.27",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.26",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.25",
"platformVersions": [
"*"
],
"defaultVersion": true
},
{
"clusterVersion": "1.24",
"platformVersions": [
"*"
],
"defaultVersion": true
}
],
"requiresConfiguration": false,
"requiresIamPermissions": false
}
],
"publisher": "eks",
"owner": "community"
}
]
}
特に設定を行わない場合、レプリカ数 2 の deployment として metrics-server が起動されました。
% kubectl describe deployment -n kube-system
Name: metrics-server
Namespace: kube-system
CreationTimestamp: Sun, 05 Jan 2025 17:03:08 +0900
Labels: app.kubernetes.io/instance=metrics-server
app.kubernetes.io/managed-by=EKS
app.kubernetes.io/name=metrics-server
app.kubernetes.io/version=0.7.2
Annotations: deployment.kubernetes.io/revision: 1
Selector: app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
Replicas: 2 desired | 2 updated | 2 total | 0 available | 2 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 25% max surge
Pod Template:
Labels: app.kubernetes.io/instance=metrics-server
app.kubernetes.io/name=metrics-server
Service Account: metrics-server
Containers:
metrics-server:
Image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1
Port: 10250/TCP
Host Port: 0/TCP
SeccompProfile: RuntimeDefault
Args:
--secure-port=10250
--cert-dir=/tmp
--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
--kubelet-use-node-status-port
--metric-resolution=15s
Limits:
memory: 400Mi
Requests:
cpu: 100m
memory: 200Mi
Liveness: http-get https://:https/livez delay=0s timeout=1s period=10s #success=1 #failure=3
Readiness: http-get https://:https/readyz delay=20s timeout=1s period=10s #success=1 #failure=3
Environment: <none>
Mounts:
/tmp from tmp (rw)
Volumes:
tmp:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
Topology Spread Constraints: topology.kubernetes.io/zone:ScheduleAnyway when max skew 1 is exceeded for selector app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
Priority Class Name: system-cluster-critical
Conditions:
Type Status Reason
---- ------ ------
Available False MinimumReplicasUnavailable
Progressing True ReplicaSetUpdated
OldReplicaSets: <none>
NewReplicaSet: metrics-server-c996b895d (2/2 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 42s deployment-controller Scaled up replica set metrics-server-c996b895d to 2
Pod の情報も見てみます。
% kubectl describe pod metrics-server-c996b895d-5vxxr -n kube-system
Name: metrics-server-c996b895d-5vxxr
Namespace: kube-system
Priority: 2000000000
Priority Class Name: system-cluster-critical
Service Account: metrics-server
Node: i-008cc696c613aec02/10.0.100.6
Start Time: Sun, 05 Jan 2025 17:03:44 +0900
Labels: app.kubernetes.io/instance=metrics-server
app.kubernetes.io/name=metrics-server
pod-template-hash=c996b895d
Annotations: <none>
Status: Running
IP: 10.0.100.48
IPs:
IP: 10.0.100.48
Controlled By: ReplicaSet/metrics-server-c996b895d
Containers:
metrics-server:
Container ID: containerd://99309ed2fb57b4ddad642b49fda7b08c6198aceb7f6a871fbb74922d406fa13a
Image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1
Image ID: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server@sha256:ddfec9fafbc354a0627dff19826ce257ac656ba3190150c115f177fb40cc4703
Port: 10250/TCP
Host Port: 0/TCP
SeccompProfile: RuntimeDefault
Args:
--secure-port=10250
--cert-dir=/tmp
--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
--kubelet-use-node-status-port
--metric-resolution=15s
State: Running
Started: Sun, 05 Jan 2025 17:03:51 +0900
Ready: True
Restart Count: 0
Limits:
memory: 400Mi
Requests:
cpu: 100m
memory: 200Mi
Liveness: http-get https://:https/livez delay=0s timeout=1s period=10s #success=1 #failure=3
Readiness: http-get https://:https/readyz delay=20s timeout=1s period=10s #success=1 #failure=3
Environment: <none>
Mounts:
/tmp from tmp (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jwfj2 (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
tmp:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
kube-api-access-jwfj2:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: CriticalAddonsOnly op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Topology Spread Constraints: topology.kubernetes.io/zone:ScheduleAnyway when max skew 1 is exceeded for selector app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Nominated 2m24s karpenter Pod should schedule on: nodeclaim/system-q79fl
Warning FailedScheduling 2m10s (x5 over 2m25s) default-scheduler no nodes available to schedule pods
Warning FailedScheduling 2m default-scheduler 0/2 nodes are available: 2 node(s) had untolerated taint {karpenter.sh/unregistered: }. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling.
Normal Nominated 117s karpenter Pod should schedule on: nodeclaim/system-lw8b2, node/i-008cc696c613aec02
Normal Scheduled 110s default-scheduler Successfully assigned kube-system/metrics-server-c996b895d-5vxxr to i-008cc696c613aec02
Normal Pulling 109s kubelet Pulling image "602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1"
Normal Pulled 103s kubelet Successfully pulled image "602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1" in 5.341s (5.341s including waiting). Image size: 17865884 bytes.
Normal Created 103s kubelet Created container metrics-server
Normal Started 103s kubelet Started container metrics-server
CriticalAddonsOnly の toleration が設定されているため、クラスター運用のために必要な Pod のみを配置する system NodePool 管理のノードに配置されています。
この状態で top コマンドを実行すると、ノードの情報を取得できました!
% kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
i-008cc696c613aec02 12m 0% 267Mi 8%
i-083fad0420bccb48d 11m 0% 268Mi 8%
pod の情報も取得できています。
% kubectl top pod -n kube-system
NAME CPU(cores) MEMORY(bytes)
metrics-server-c996b895d-5vxxr 2m 16Mi
metrics-server-c996b895d-dhcpp 2m 16Mi
アドオンの設定を変更する
続いて、アドオンの設定を変更してみます。
アドオンの設定スキーマは下記です(2024/1/5 現在)。
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"additionalProperties": false,
"definitions":
{
"limits":
{
"additionalProperties": false,
"properties":
{ "cpu": { "type": "string" }, "memory": { "type": "string" } },
"title": "limits",
"type": "object",
},
"resources":
{
"additionalProperties": false,
"properties":
{
"limits": { "$ref": "#/definitions/limits" },
"requests": { "$ref": "#/definitions/limits" },
},
"title": "resources",
"type": "object",
},
},
"description": "Configurable properties for metrics-server",
"properties":
{
"addonResizer":
{
"additionalProperties": false,
"properties":
{
"enabled":
{
"default": false,
"description": "Whether or not to enable the addon-resizer",
"type": "boolean",
},
"resources":
{
"$ref": "#/definitions/resources",
"description": "Resource requests/limits of the addon resizer container",
},
},
"required": ["enabled"],
"type": "object",
},
"affinity":
{
"default":
{
"nodeAffinity":
{
"requiredDuringSchedulingIgnoredDuringExecution":
{
"nodeSelectorTerms":
[
{
"matchExpressions":
[
{
"key": "kubernetes.io/os",
"operator": "In",
"values": ["linux"],
},
{
"key": "kubernetes.io/arch",
"operator": "In",
"values": ["amd64", "arm64"],
},
],
},
],
},
},
"podAntiAffinity":
{
"preferredDuringSchedulingIgnoredDuringExecution":
[
{
"podAffinityTerm":
{
"labelSelector":
{
"matchExpressions":
[
{
"key": "app.kubernetes.io/name",
"operator": "In",
"values": ["metrics-server"],
},
],
},
"topologyKey": "kubernetes.io/hostname",
},
"weight": 100,
},
],
},
},
"description": "Affinity of the metrics-server pod",
"type": ["object", "null"],
},
"nodeSelector":
{
"description": "Node selector of the metrics-server pod",
"type": ["object", "null"],
},
"podAnnotations":
{
"additionalProperties": { "type": "string" },
"description": "Additional annotations to add to the metrics-server deployment pods",
"type": "object",
},
"podDisruptionBudget":
{
"additionalProperties": false,
"description": "Settings for the PodDisruptionBudget",
"enabled":
{
"default": true,
"description": "the option to enable managed PDB",
"type": "boolean",
},
"maxUnavailable":
{
"anyOf":
[
{ "pattern": ".*%$", "type": "string" },
{ "type": "integer" },
],
"default": 1,
"description": "maxUnavailable value for managed PDB, can be either string or integer; if it's string, should end with %",
},
"minAvailable":
{
"anyOf":
[
{ "pattern": ".*%$", "type": "string" },
{ "type": "integer" },
],
"description": "minAvailable value for managed PDB, can be either string or integer; if it's string, should end with %",
},
"type": "object",
},
"podLabels":
{
"additionalProperties": { "type": "string" },
"description": "Additional labels to add to the metrics-server deployment pods",
"type": "object",
},
"replicas":
{
"default": 2,
"description": "Number of replicas in the metrics-server deployment",
"minimum": 1,
"type": "integer",
},
"resources":
{
"$ref": "#/definitions/resources",
"description": "Resource requests/limits of the metrics-server container",
},
"tolerations":
{
"additionalProperties": false,
"default": [{ "key": "CriticalAddonsOnly", "operator": "Exists" }],
"description": "Tolerations of the metrics-server pod",
"items": { "type": "object" },
"type": "array",
},
"topologySpreadConstraints":
{
"default":
[
{
"labelSelector":
{
"matchLabels":
{
"app.kubernetes.io/instance": "metrics-server",
"app.kubernetes.io/name": "metrics-server",
},
},
"maxSkew": 1,
"topologyKey": "topology.kubernetes.io/zone",
"whenUnsatisfiable": "ScheduleAnyway",
},
],
"description": "The coredns pod topology spread constraints",
"type": "array",
},
},
"type": "object",
}
本番環境ではクラスターの動作に必要な Pod を専用ノードに配置するべきかもしれませんが、検証用クラスターでは過剰に思います。
また、冗長化も不要なため、下記設定に変更します。
{
"replicas": 1,
"tolerations": []
}
Pod 数も減って、general-purpose NodePool で管理されたインスタンスのみが存在する形になりました。
% kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
metrics-server-9f6cbf79-g5z72 1/1 Running 0 7m32s
設定が良い感じに反映されてますね!
Horizontal Pod Autoscaler(HPA) を利用してみる
まず、Nginx をデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: nginx
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: "0.5"
この状態では、Pod が一つだけ存在します。
% kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-64549c5599-hk8h2 1/1 Running 0 12s
HPA を設定します。
% kubectl autoscale deployment nginx --cpu-percent=50 --min=1 --max=5
horizontalpodautoscaler.autoscaling/nginx autoscaled
まだ Pod が一つだけ存在する状態です。
% kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
nginx Deployment/nginx cpu: 0%/50% 1 5 1 24s
Pod に乗り込んで yes > /dev/null
で CPU 負荷をかけます。
% kubectl exec -it nginx-64549c5599-hk8h2 -- /bin/bash
root@nginx-64549c5599-hk8h2:/# yes > /dev/null
すぐに Pod がスケールアウトしました(特に設定していないので、15 秒間隔でスケールアウトするかの判断が発生するはずです)。
% kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-64549c5599-bcbfk 1/1 Running 0 40s
nginx-64549c5599-hk8h2 1/1 Running 0 5m48s
nginx-64549c5599-jlwrg 0/1 ContainerCreating 0 40s
nginx-64549c5599-k5gkf 1/1 Running 0 40s
CPU 負荷が上がっている様子も確認できます。
% kubectl top pod
NAME CPU(cores) MEMORY(bytes)
nginx-64549c5599-bcbfk 0m 1Mi
nginx-64549c5599-hk8h2 998m 2Mi
nginx-64549c5599-jlwrg 2m 1Mi
nginx-64549c5599-k5gkf 0m 1Mi
合わせて Node 側もスケーリングしています。
Pod が一つだけ ContainerCreating で止まっていたのは Node のプロビジョニングが走ったからですね。
% kubectl get node
NAME STATUS ROLES AGE VERSION
i-00c6beb080e200030 Ready <none> 83s v1.31.1-eks-1b3e656
i-03528a7559f9a331b Ready <none> 29m v1.31.1-eks-1b3e656
metrics-server アドオン追加時にプロビジョニングされた c5a.large は 2vCPU のインスタンスです。
0.5vCPU を要求する Nginx の Pod が 3 つと 0.1 vCPU を要求する metrics-server でほぼいっぱいなので、次の Nginx の Pod を作成する前にノードのスケールが走った形だと思います。
※ 0.5×3+0.1=1.6(vCPU) を使われているので、次の Nginx(0.5vCPU) が起動できない。
Ctl + C で yes コマンドを停止すると、5 分程度でスケールインしました(こちらも特に設定していないので、5 分間隔でスケールインするかの判断が発生するはずです)。
% kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-64549c5599-hk8h2 1/1 Running 0 19m
Node も不要になったので、合わせてスケールインしてますね。
% kubectl get node
NAME STATUS ROLES AGE VERSION
i-00c6beb080e200030 Ready <none> 17m v1.31.1-eks-1b3e656
まとめ
metrics-server をアドオンとして扱えるようにして欲しいというのは、containers-roadmap に 2019 年から存在して 3 桁のリアクションがつくような、多くの人に望まれた機能でした。
Community アドオンという新しい概念とともに対応されたのはめちゃくちゃ嬉しいです。
サポート範囲が限定的とはいえ、インストール時の面倒は見てくれるそうなので、上手く使えば大きく工数を減らすことが可能です。
現時点では metrics-server だけですが、他 OSS への拡張も期待できます。
metrics-server と同様に kubernetes-sigs リポジトリで管理されている external-dns 辺りにも拡張されると嬉しいなと思いました。
後は Argo CD 辺りもこの枠組みで対応してくれたら嬉しいですね。
EKS セットアップ時の負荷がどんどん減っててすごい!!